#![allow(dead_code)]

use gl;
use std::mem::{size_of_val, size_of};
use std::ffi::c_void;
use std::ptr;
use crate::shaders::{TEX_COORD_INPUT_POSITION, VERTEX_INPUT_POSITION};
use zmath::Vec2;

#[repr(u8)]
pub enum VertexLayout {
    P,
    P1C,
    P1UV,
}

pub struct VertexArray {
    vao: u32,
    vbo: u32,
    ibo: u32,
    layout: VertexLayout,
}

impl VertexArray {
    pub fn new(layout: VertexLayout) -> Self {
        let (vao, vbo, ibo) = unsafe {
            let mut vao = 0;
            let mut buffers = [0; 2];

            gl::GenVertexArrays(1, &mut vao);
            gl::GenBuffers(2, buffers.as_mut_ptr());

            (vao, buffers[0], buffers[1])
        };

        Self {
            vao,
            vbo,
            ibo,
            layout
        }
    }

    pub fn quad(pos: Vec2, extent: Vec2) ->  Self {
        let vao = Self::new(VertexLayout::P1UV);

        let left = pos.0 - extent.0 / 2.0;
        let bottom = pos.1 - extent.1 / 2.0;
        let right = pos.0 + extent.0 / 2.0;
        let top = pos.1 + extent.1 / 2.0;

        let shape = [
            left, bottom,      0.0, 1.0,
            left, top,       0.0, 0.0,
            right, top,        1.0, 0.0,
            right, bottom,       1.0, 1.0
        ];

        let indices = [
            0, 1, 2,
            2, 3, 0
        ];

        vao.set_triangles(&shape, &indices);

        vao
    }

    pub fn set_triangles(&self, vertices: &[f32], indices: &[u32]) {
        unsafe {
            gl::BindVertexArray(self.vao);
            gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
            gl::BufferData(gl::ARRAY_BUFFER, size_of_val(vertices) as isize, vertices.as_ptr() as *const c_void, gl::STATIC_DRAW);

            gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ibo);
            gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, size_of_val(indices) as isize, indices.as_ptr() as *const c_void, gl::STATIC_DRAW);
        }
    }

    pub fn bind(&self) {
        unsafe {
            gl::BindVertexArray(self.vao);
            gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ibo);

            match self.layout {
                VertexLayout::P => {
                    gl::VertexAttribPointer(VERTEX_INPUT_POSITION, 2, gl::FLOAT, gl::FALSE, 2 * size_of::<f32>() as i32, ptr::null());
                    gl::EnableVertexAttribArray(VERTEX_INPUT_POSITION);
                }
                VertexLayout::P1C => {
                    gl::VertexAttribPointer(VERTEX_INPUT_POSITION, 2, gl::FLOAT, gl::FALSE, 6 * size_of::<f32>() as i32, ptr::null());
                    gl::VertexAttribPointer(1, 4, gl::FLOAT, gl::FALSE, 6 * size_of::<f32>() as i32, (2 * size_of::<f32>()) as *const c_void);
                    gl::EnableVertexAttribArray(VERTEX_INPUT_POSITION);
                    gl::EnableVertexAttribArray(1);
                }
                VertexLayout::P1UV => {
                    gl::VertexAttribPointer(VERTEX_INPUT_POSITION, 2, gl::FLOAT, gl::FALSE, 4 * size_of::<f32>() as i32, ptr::null());
                    gl::VertexAttribPointer(TEX_COORD_INPUT_POSITION, 2, gl::FLOAT, gl::FALSE, 4 * size_of::<f32>() as i32, (2 * size_of::<f32>()) as *const c_void);
                    gl::EnableVertexAttribArray(VERTEX_INPUT_POSITION);
                    gl::EnableVertexAttribArray(TEX_COORD_INPUT_POSITION);
                }
            }
        }
    }

    pub fn unbind(&self) {
        unsafe {
            gl::BindVertexArray(0);
            gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
        }
    }

    pub fn draw(&self, primitive_type: u32, num_vertices: i32) {
        unsafe {
            gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ibo);
            gl::DrawElements(primitive_type, num_vertices, gl::UNSIGNED_INT, ptr::null());
        }
    }
}

impl Drop for VertexArray {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteBuffers(2, [self.vbo, self.ibo].as_ptr());
            gl::DeleteVertexArrays(1, &self.vao);
        }
    }
}